﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;

namespace IMDBApi.Contracts
{
    /// <summary>
    /// A class which provides all the functions to interact with the more special features of the IMDb iPhone API.
    /// </summary>
    public class IMDBFeatures 
        : IMDBBase
    {
        #region Public

        /// <summary>
        /// Get showtimes for a given date and location formatted as a JSON string.
        /// 
        /// A Location (US,33333) is required
        /// </summary>
        /// <param name="location">Location in the following format "US,33333".</param>
        /// <param name="date">(Optional)  Date in the sql format "2009-12-24".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetShowTimes(string location, string date = null)
        {
            // checks the given date or sets today
            if (!CheckDate(date))
            {
                date = DateTime.Now.ToString("yyyy-MM-dd");
            }

            // checks the location or throws exception
            if (!CheckLocation(location))
            {
                throw new Exception(MethodBase.GetCurrentMethod().Name + ": A location is required.");
            }

            Dictionary<string, string> arg = new Dictionary<string, string>
            {
                        { "date", date },
                        { "location", location }
                };
            return MakeRequest("/showtimes/location", arg);
        }

        /// <summary>
        /// Get a list of movies coming soon formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetComingSoon()
        {
            return MakeRequest("/feature/comingsoon");
        }

        /// <summary>
        /// Get current box office results for a specified region formatted as a JSON string.
        /// </summary>
        /// <param name="boxOfficeRegion">(Optional) The boxoffice region formatted in two character country code "US".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetBoxOfficeResults(string boxOfficeRegion = null)
        {
            // checks the given Box Office Region or sets "US"
            if (!CheckBoxOfficeRegion(boxOfficeRegion))
            {
                boxOfficeRegion = "US";
            }

            Dictionary<string, string> arg = new Dictionary<string, string> { { "boxoffice_region", boxOfficeRegion } };
            return MakeRequest("/boxoffice", arg);
        }

        /// <summary>
        /// Get the Moviemeter list formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetMovieMeter()
        {
            return MakeRequest("/chart/moviemeter");
        }

        /// <summary>
        /// Get the latest top 250 movies list formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetTop250Movies()
        {
            return MakeRequest("/chart/top");
        }

        /// <summary>
        /// Get genre list formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetGenres()
        {
            Dictionary<string, string> arg = new Dictionary<string, string> { { "type", "genre" } };
            return MakeRequest("/keys", arg);
        }

        /// <summary>
        /// Get popular movies by genre formatted as a JSON string.
        /// </summary>
        /// <param name="genre">Required genre.</param>
        /// <returns>JSON formatted string.</returns>
        public string GetPopularMoviesByGenre(string genre = "action")
        {
            Dictionary<string, string> arg = new Dictionary<string, string> { { "type", genre } };
            return MakeRequest("/moviegenre", arg);
        }

        /// <summary>
        /// Get the latest bottom 100 movies list formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetBottom100Movies()
        {
            return MakeRequest("/chart/bottom");
        }

        /// <summary>
        /// Get the latest DVD & BluRay Releases formatted as a JSON string.
        /// </summary>
        /// <param name="marketplace"></param>
        /// <returns>JSON formatted string.</returns>
        public string GetDvdBlurayNewReleases(string marketplace = "US")
        {
            Dictionary<string, string> arg = new Dictionary<string, string>
            {
                    { "date", DateTime.Now.ToString(CultureInfo.InvariantCulture) },
                    { "marketplace", marketplace }
                };
            return MakeRequest("/products/new_releases", arg);
        }

        /// <summary>
        /// Get the latest DVD Bestsellers formatted as a JSON string.
        /// </summary>
        /// <param name="marketplace"></param>
        /// <returns>JSON formatted string.</returns>
        public string GetDvdBestsellers(string marketplace = "US")
        {
            Dictionary<string, string> arg = new Dictionary<string, string>
            {
                    { "marketplace", marketplace },
                    { "media", "dvd" }
                };
            return MakeRequest("/products/bestsellers", arg);
        }

        /// <summary>
        /// Get the latest BluRay Bestsellers formatted as a JSON string.
        /// </summary>
        /// <param name="marketplace"></param>
        /// <returns>JSON formatted string.</returns>
        public string GetBlurayBestsellers(string marketplace = "US")
        {
            Dictionary<string, string> arg = new Dictionary<string, string>
            {
                    { "marketplace", marketplace },
                    { "media", "blu_ray" }
                };
            return MakeRequest("/products/bestsellers", arg);
        }

        /// <summary>
        /// Get the all Best Picture Winners formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetBestPictureWinners()
        {
            return MakeRequest("/feature/best_picture");
        }

        /// <summary>
        /// Get the tv program for a given night formatted as a JSON string.
        /// </summary>
        /// <param name="date">(Optional) Date in the sql format "2009-12-24".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetUsTvTonight(string date = null)
        {
            // checks the given date or sets today
            if (!CheckDate(date))
            {
                date = DateTime.Now.ToString("yyyy-MM-dd");
            }

            Dictionary<string, string> arg = new Dictionary<string, string> { { "date", date } };
            return MakeRequest("/tv/tonight", arg);
        }

        /// <summary>
        /// Get tv recaps for a given night formatted as a JSON string.
        /// </summary>
        /// <param name="date">(Optional) Date in the sql format "2009-12-24".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetUsTvRecaps(string date = null)
        {
            // checks the given date or sets today
            if (!CheckDate(date))
            {
                date = DateTime.Now.ToString("yyyy-MM-dd");
            }

            Dictionary<string, string> arg = new Dictionary<string, string> { { "date", date } };
            return MakeRequest("/tv/recap", arg);
        }

        /// <summary>
        /// Get popular tv shows formatted as a JSON string.
        /// </summary>
        /// <param name="date">(Optional) Date in the sql format "2009-12-24".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetPopularTv(string date = null)
        {
            return MakeRequest("/chart/tv");
        }

        /// <summary>
        /// Get the latest Starmeter list formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetStarMeter()
        {
            return MakeRequest("/chart/starmeter");
        }

        /// <summary>
        /// Get a list of people born at the given date formatted as a JSON string.
        /// </summary>
        /// <param name="date">(Optional) Date in the sql format "2009-12-24".</param>
        /// <returns>JSON formatted string.</returns>
        public string GetBornToday(string date = null)
        {
            // checks the given date or sets today
            if (!CheckDate(date))
            {
                date = DateTime.Now.ToString("yyyy-MM-dd");
            }

            Dictionary<string, string> arg = new Dictionary<string, string> { { "date", date } };
            return MakeRequest("/feature/borntoday", arg);
        }

        /// <summary>
        /// Get the latest news formatted as a JSON string.
        /// </summary>
        /// <returns>JSON formatted string.</returns>
        public string GetNews()
        {
            return MakeRequest("/news");
        }

        #endregion

        #region Protected

        /// <summary>
        /// Checks if the given location is in a valid format
        /// </summary>
        /// <param name="location">Location in following format "US,33333".</param>
        /// <returns>True if Location is alright. False if there is a problem with it.</returns>
        protected bool CheckLocation(string location)
        {
            if (location == string.Empty)
            {
                return false;
            }

            if (!Regex.Match("/^([A-Z]{2}),([0-9]{5})$/", location).Success)
            {
                throw new Exception(MethodBase.GetCurrentMethod().Name + ": expects a Location formatted like US,33333.");
            }
            return true;
        }

        /// <summary>
        /// Checks if the given date is in a valid format
        /// </summary>
        /// <param name="date">Date in the sql format "2009-12-24".</param>
        /// <returns>True if Date is alright. False if there is a problem with it.</returns>
        protected bool CheckDate(string date)
        {
            if (date == string.Empty)
            {
                return false;
            }
            
            if (!Regex.Match("/^([1-3][0-9]{3,3})-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][1-9]|3[0-1])$/", date).Success)
            {
                throw new Exception(MethodBase.GetCurrentMethod().Name + ": expects a SQL formatted date like 2009-12-24.");
            }
            return true;
        }

        /// <summary>
        /// Checks if the given Boxoffice Region is in a valid format
        /// </summary>
        /// <param name="boxOfficeRegion">Boxoffice Region in following format "US".</param>
        /// <returns>True if Boxoffice Region is alright. False if there is a problem with it.</returns>
        protected bool CheckBoxOfficeRegion(string boxOfficeRegion)
        {
            if (boxOfficeRegion == string.Empty)
            {
                return false;
            }
            
            if (!Regex.Match("/^([A-Z]{2})?$/", boxOfficeRegion).Success)
            {
                throw new Exception(MethodBase.GetCurrentMethod().Name + ": Format should be XX. For example US.");
            }
            return true;
        }

        #endregion
    }
}